/* Parallel Elliptic Curve Cryptography Engine - An OpenSSL Engine which
 * uses the SPU Elliptic Curve Library to provide optimized functionality
 * on the CBEA.
 *
 * Copyright (C) 2008  Michael Annichiarico (mike.gcdeveloper@gmail.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <spu_intrinsics.h>
#include <spu_mfcio.h>

#include<libmpm.h>
#include"pecc-stage4_spu.h"
#include<stdio.h>

#include"./spec/include/spec.h"

static const vector unsigned char BN2SPECendianswap = {
		0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
		0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07};
#define doBNSPECswap_on_arg(A) \
	arg->A.v[0] = spu_shuffle(arg->A.v[0],arg->A.v[0],BN2SPECendianswap); \
	arg->A.v[1] = spu_shuffle(arg->A.v[1],arg->A.v[1],BN2SPECendianswap);
#define doBNMPMswap_on_arg(A) \
	arg->A.v[0] = spu_shuffle(arg->A.v[0],arg->A.v[0],BN2SPECendianswap); \
	arg->A.v[1] = spu_shuffle(arg->A.v[1],arg->A.v[1],BN2SPECendianswap); \
	arg->A.v[0] = spu_xor(arg->A.v[0],arg->A.v[1]); \
	arg->A.v[1] = spu_xor(arg->A.v[0],arg->A.v[1]); \
	arg->A.v[0] = spu_xor(arg->A.v[0],arg->A.v[1]); 
/*
static const vector unsigned char BN2SPECendianswap = {
		0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
		0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07};
#define doBNSPECswap_on_arg(A) \
	arg->A.v[0] = spu_shuffle(arg->A.v[0],arg->A.v[0],BN2SPECendianswap); \
	arg->A.v[1] = spu_shuffle(arg->A.v[1],arg->A.v[1],BN2SPECendianswap)
*/

/*
void mpmtest() {
	vector unsigned int r;
	vector unsigned int a={1,1,1,1};
	vector unsigned int b={2,2,2,2};
	vector unsigned int s={5,5,5,5};
	r = mpm_add(&s,&a,&b,sizeof(a));
	printf("MPM_TEST ADD RESULT: %vu\n",s);
}
*/
//enum PECC_SPU_CMD_TYPE {ADD};

void pecc_spu_put_list_atomic(void* src, unsigned int eah, unsigned int* list, unsigned int size) {
	unsigned int tag_id;
	tag_id = mfc_tag_reserve();
	mfc_putl(src, eah, list, size, tag_id, 0, 0);
	mfc_write_tag_mask(1<<tag_id);
	mfc_read_tag_status_all();
}

void pecc_spu_get_list_atomic(void* dst, unsigned int eah, unsigned int* list, unsigned int size) {
	unsigned int tag_id;
	tag_id = mfc_tag_reserve();
	mfc_getl(dst, eah, list, size, tag_id, 0, 0);
	mfc_write_tag_mask(1<<tag_id);
	mfc_read_tag_status_all();
}

void pecc_spu_put_atomic(void* src, unsigned long long ea, unsigned int size) {
	unsigned int tag_id;
	tag_id = mfc_tag_reserve();
	mfc_put(src, ea, size, tag_id, 0, 0);
	mfc_write_tag_mask(1<<tag_id);
	mfc_read_tag_status_all();
}

void pecc_spu_get_atomic(void* dst, unsigned long long ea, unsigned int size) {
	unsigned int tag_id;
	tag_id = mfc_tag_reserve();
	mfc_get(dst, ea, size, tag_id, 0, 0);
	mfc_write_tag_mask(1<<tag_id);
	mfc_read_tag_status_all();
}
/* //not working yet
void pecc_spu_put_list(const spu_list_t* list,void* src) {
	unsigned int dmalist[PECC_DMALIST_MAXSIZE*2];
	unsigned int eah = list->list[0].data.by32[0];
	int i;
	for(i=0;i<list->size;++i) {
		dmalist[2*i] = list->list[i].size;
		dmalist[2*i+1] = list->list[i].data.by32[1];	
		if(list->list[i].data.by32[0]!=eah) printf("error eah doesnt match: el: %d\n",i);
	}
	pecc_spu_put_list_atomic(src, eah, dmalist, list->size);
}
*/
void pecc_spu_put_list(const spu_list_t* list,void* src) {
	int i;
	for(i=0;i<list->size;++i) {
		pecc_spu_put_elem(&list->list[i],src);
		src=(void*)((unsigned long long)src + list->list[i].size);
	}
}
/* //doesnt work yet
void pecc_spu_get_list(void* dst, const spu_list_t* list) {
	unsigned int dmalist[PECC_DMALIST_MAXSIZE*2];
	unsigned int eah = list->list[0].data.by32[0];
	int i;
	for(i=0;i<list->size;++i) {
		dmalist[2*i] = list->list[i].size;
		dmalist[2*i+1] = list->list[i].data.by32[1];	
		printf("list element: addr: %08x %08x :: size: %04x\n",eah, dmalist[2*i+1],dmalist[2*i]);
		if(list->list[i].data.by32[0]!=eah) printf("error eah doesnt match: el: %d\n",i);
	}
	printf("sizeof void* : %d\n",sizeof(void*));
	printf("here2.6: dst: %08x %08x\n",dst,((unsigned int*)(&dst))[1]);
	pecc_spu_get_list_atomic(dst, eah, dmalist, list->size);
}
*/ //alternate version
void pecc_spu_get_list(void* dst, const spu_list_t* list) {
	int i;
//	printf("get_list: list = %p\n",list);
//	printf("list->size = %d\n",list->size);
	for(i=0;i<list->size;++i) {
		pecc_spu_get_elem(dst,&list->list[i]);
		dst=(void*)((unsigned long long)dst + list->list[i].size);
	}
}
void pecc_spu_put_elem(const spu_elem_t* el,void* src) {
//	printf("put_elem: src = 0x%08x, el = 0x%08x.  addr= 0x%016lx size=0x%04x\n",src,el,el->data.all64,el->size);
//	printf("put_elem: src = 0x%08x, el = 0x%08x.  addr= 0x%08x %08x size=0x%04x\n",src,el,el->data.by32[0],el->data.by32[1],el->size);
	pecc_spu_put_atomic(src, el->data.all64, el->size);
}
void pecc_spu_get_elem(void* dst, const spu_elem_t* el) {
//	printf("get_elem: dst = 0x%08x, el = 0x%08x.  addr= 0x%016lx size=0x%04x\n",dst,el,el->data.all64,el->size);
//	printf("get_elem: dst = 0x%08x, el = 0x%08x.  addr= 0x%08x %08x size=0x%04x\n",dst,el,el->data.by32[0],el->data.by32[1],el->size);
	pecc_spu_get_atomic(dst, el->data.all64, el->size);
}

void pecc_dispatch_cmd(unsigned long long argp, spu_cmd_t* cmdp) {
	mkpsfuncarg(add);
	mkpsfuncarg(mod_mult);
	mkpsfuncarg(ecdsa_sign);
	mkpsfuncarg(ecdsa_verify);
	mkpsfuncarg(scalar_mult);

	pecc_spu_get_atomic(cmdp,argp,sizeof(spu_cmd_t));

#define do_cmd(n) \
		/* printf("cmdd->type == " #n "\n"); */ \
		mkpsgfuncname(n)(&spu_##n##_arg,&(cmdp->arg)); \
		mkpsfuncname(n)(&spu_##n##_arg); \
		mkpssfuncname(n)(&spu_##n##_arg,&(cmdp->arg))

	switch(cmdp->type) {
	case 0:
		do_cmd(add);
		break;
	case 1:
		do_cmd(mod_mult);
		break;
	case 2:
		do_cmd(ecdsa_sign);
		break;
	case 3:
		do_cmd(ecdsa_verify);
		break;
	case 4:
		do_cmd(scalar_mult);
		break;
	default:
		printf("ERROR: spu cmd cmdd->type == %d\n",cmdp->type);
		break;
	};

	(void)spu_mfcstat(MFC_TAG_UPDATE_ALL);
}

#define vpr(v)
/*
#define vpr(v) printf("vector " #v "= %08x%08x%08x%08x\n", \
	((unsigned int*)v)[0],((unsigned int*)v)[1],((unsigned int*)v)[2],((unsigned int*)v)[3])
*/

//void pecc_spu_add(spu_add_t* arg) {
mkpsfunc(add) {
	vui* s = (vui*) &(arg->s);
	vui* a =  (vui*) &(arg->a);
	vui* b = (vui*) &(arg->b);
	int size = (int) arg->size;
	vpr(s);
	vpr(a);
	vpr(b);
	vector unsigned int r = mpm_add(s,a,b,size);
	vpr(s);
	arg->s = *s;
	vpr(&(arg->s));
}

//void pecc_spu_add_get_args(spu_add_t* arg, spu_elem_t* el) {
mkpsgfunc(add) {
	pecc_spu_get_elem((void*)arg, el);
}
//void pecc_spu_add_set_result(spu_add_t* arg, spu_elem_t* el) {
mkpssfunc(add) {
	pecc_spu_put_elem(el, (void*)arg);
}

mkpsfunc(mod_mult) {
	vpr((arg->s.v));
	vpr((arg->a.v));
	vpr((arg->b.v));
	vpr((arg->m.v));
	//mpm_mul(arg->s.v,arg->a.v,arg->a.size,arg->b.v,arg->b.size);
	vpr((arg->s.v));
	//mpm_mod(arg->s.v,arg->s.v,arg->s.size,arg->m.v,arg->m.size);
	vpr((arg->s.v));
}

mkpsgfunc(mod_mult) {
	spu_list_t list;
	pecc_spu_get_elem((void*)&list, el);
	pecc_spu_get_list((void*)arg, &list);
}
mkpssfunc(mod_mult) {
//	pecc_spu_put_elem(el, (void*)arg);
}

static spu_list_t ecdsa_sign_arglist;
mkpsfunc(ecdsa_sign) {
	int result;
/*
	printf("here1\n");
	vpr((arg->d.v));
	vpr((arg->e.v));
	vpr((arg->k.v));
	arg->r.a[0]=0xa;
	arg->r.a[1]=0xb;
	arg->r.a[2]=0xc;
	arg->r.a[3]=0xd;
	arg->s.a[0]=0x1a;
	arg->s.a[1]=0x1b;
	arg->s.a[2]=0x1c;
	arg->s.a[3]=0x1d;
	vpr((arg->r.v));
	vpr((arg->s.v));
*/
	vpr((arg->d.v));
	vpr((arg->d.v+1));
	vpr((arg->e.v));
	vpr((arg->e.v+1));
	vpr((arg->k.v));
	vpr((arg->k.v+1));
	result = spec_ecdsa_sign(&nistb233grp,
					(PFIELD2M)arg->k.v, (PFIELD2M)arg->d.v,(PFIELD2M)arg->e.v,
				(PFIELD2M)arg->r.v,(PFIELD2M)arg->s.v );
//	printf("from SPU: ecdsa_sign result: %d\n",result);
	vpr((arg->r.v));
	vpr((arg->r.v+1));
	vpr((arg->s.v));
	vpr((arg->s.v+1));
}

mkpsgfunc(ecdsa_sign) {
	//unsigned int __attribute__((aligned(128)))  testing[1024];
//	__attribute__((aligned(128))) unsigned int __attribute__((aligned(128))) testing[1024] __attribute__((aligned(128)));
//	struct myals {unsigned int v[1024];} __attribute__((aligned(128))) testing;
//	unsigned char* pp = (unsigned char*)testing.v; pp+=128; pp=(unsigned char*) (((unsigned int)pp) & 0xFFFFFF80);
//	printf("addr 1x: %08X\naddr 2: %08X\naddr 3: %08X\n",testing.v,testing.v+1,testing.v+2);
//	printf("here2.0\n");
	pecc_spu_get_elem((void*)&ecdsa_sign_arglist, el);
//	printf("here2.5\n");
//	pecc_spu_get_list((void*)pp, &list);
//	pecc_spu_get_list((void*)arg, &ecdsa_sign_arglist);
	memset(arg,0,sizeof(*arg));
	pecc_spu_get_elem(&arg->d,ecdsa_sign_arglist.list+0);
	pecc_spu_get_elem(&arg->e,ecdsa_sign_arglist.list+1);
	pecc_spu_get_elem(&arg->k,ecdsa_sign_arglist.list+2);
	//fix endianness from BN types to SPEC types
	{
		FIELD2M k3 = field2m_init_ull4be(0,0,0,3);
	doBNMPMswap_on_arg(d);
	doBNMPMswap_on_arg(e);
	doBNMPMswap_on_arg(k);
	//TESTING
//	memcpy(arg->k.v,&k3,sizeof(PECC_BN_t));
	//ENDTEST
	}
}

mkpssfunc(ecdsa_sign) {
	doBNMPMswap_on_arg(r);
	doBNMPMswap_on_arg(s);
	pecc_spu_put_elem(ecdsa_sign_arglist.list+3,&arg->r);
	pecc_spu_put_elem(ecdsa_sign_arglist.list+4,&arg->s);
//	pecc_spu_put_elem(el, (void*)arg);
/*
	printf("here3\n");
	pecc_spu_get_elem((void*)&list, el);
	list.size=5; //return last 2 values (and copy the first 3 back -- inefficient
	pecc_spu_put_list(&list,(void*)arg);
*/
}

static spu_list_t ecdsa_verify_arglist;
mkpsfunc(ecdsa_verify) {
/*
	printf("here1\n");
	vpr((arg->d.v));
	vpr((arg->e.v));
	vpr((arg->k.v));
	arg->r.a[0]=0xa;
	arg->r.a[1]=0xb;
	arg->r.a[2]=0xc;
	arg->r.a[3]=0xd;
	arg->s.a[0]=0x1a;
	arg->s.a[1]=0x1b;
	arg->s.a[2]=0x1c;
	arg->s.a[3]=0x1d;
	vpr((arg->r.v));
	vpr((arg->s.v));
*/
	vpr((arg->qx.v));
	vpr((arg->qx.v+1));
	vpr((arg->qy.v));
	vpr((arg->qy.v+1));
	vpr((arg->e.v));
	vpr((arg->e.v+1));
	vpr((arg->r.v));
	vpr((arg->r.v+1));
	vpr((arg->s.v));
	vpr((arg->s.v+1));
	arg->result = spec_ecdsa_verify(&nistb233grp,
					(PECPOINT)arg->qx.v, (PFIELD2M)arg->e.v,
				(PFIELD2M)arg->r.v,(PFIELD2M)arg->s.v );
//	printf("from SPU: ecdsa_verify result: %d\n",arg->result);
}

mkpsgfunc(ecdsa_verify) {
	//unsigned int __attribute__((aligned(128)))  testing[1024];
//	__attribute__((aligned(128))) unsigned int __attribute__((aligned(128))) testing[1024] __attribute__((aligned(128)));
//	struct myals {unsigned int v[1024];} __attribute__((aligned(128))) testing;
//	unsigned char* pp = (unsigned char*)testing.v; pp+=128; pp=(unsigned char*) (((unsigned int)pp) & 0xFFFFFF80);
//	printf("addr 1x: %08X\naddr 2: %08X\naddr 3: %08X\n",testing.v,testing.v+1,testing.v+2);
//	printf("here2.0\n");
	pecc_spu_get_elem((void*)&ecdsa_verify_arglist, el);
//	printf("here2.5\n");
//	pecc_spu_get_list((void*)pp, &list);
	memset(arg,0,sizeof(*arg));
	//pecc_spu_get_list((void*)arg, &ecdsa_verify_arglist);
	pecc_spu_get_elem(&arg->qx,ecdsa_verify_arglist.list+0);
	pecc_spu_get_elem(&arg->qy,ecdsa_verify_arglist.list+1);
	pecc_spu_get_elem(&arg->e,ecdsa_verify_arglist.list+2);
	pecc_spu_get_elem(&arg->r,ecdsa_verify_arglist.list+3);
	pecc_spu_get_elem(&arg->s,ecdsa_verify_arglist.list+4);
	//fix endianness from BN types to SPEC types
	{ 
	doBNSPECswap_on_arg(qx);
	doBNSPECswap_on_arg(qy);
	doBNMPMswap_on_arg(e);
	doBNMPMswap_on_arg(r);
	doBNMPMswap_on_arg(s);
	}
}

mkpssfunc(ecdsa_verify) {
	pecc_spu_put_elem(ecdsa_verify_arglist.list+5,&arg->result);
//	pecc_spu_put_elem(el, (void*)arg);
/*
	printf("here3\n");
	pecc_spu_get_elem((void*)&list, el);
	list.size=5; //return last 2 values (and copy the first 3 back -- inefficient
	pecc_spu_put_list(&list,(void*)arg);
*/
}

static spu_list_t scalar_mult_arglist;
mkpsfunc(scalar_mult) {
	vpr(&arg->s.v[0]);
	vpr(&arg->s.v[1]);
	vpr(&arg->x.v[0]);
	vpr(&arg->x.v[1]);
	vpr(&arg->y.v[0]);
	vpr(&arg->y.v[1]);
	doBNSPECswap_on_arg(s);
	doBNSPECswap_on_arg(x);
	doBNSPECswap_on_arg(y);
	{
	ECPOINT Q;
	vui* s = (vui*) &(arg->s);
	vui* x =  (vui*) &(arg->x);
	vui* y = (vui*) &(arg->y);
	/*vpr(s);
	vpr(s+1);
	vpr(x);
	vpr(x+1);
	vpr(y);
	vpr(y+1);*/
	vpr(&arg->s.v[0]);
	vpr(&arg->s.v[1]);
	vpr(&arg->x.v[0]);
	vpr(&arg->x.v[1]);
	vpr(&arg->y.v[0]);
	vpr(&arg->y.v[1]);
	spec_point_MkP(&nistb233grp,(PECPOINT)&Q,(PFIELD2M)s,(PECPOINT)x);
//	x=Q.x; y=Q.y;
	vpr(&Q.x.v[0]);
	vpr(&Q.x.v[1]);
	vpr(&Q.y.v[0]);
	vpr(&Q.y.v[1]);
	memcpy(arg->x.v,Q.x.v,sizeof(arg->x));
	memcpy(arg->y.v,Q.y.v,sizeof(arg->y));
	}
	doBNSPECswap_on_arg(x);
	doBNSPECswap_on_arg(y);
	vpr(&arg->x.v[0]);
	vpr(&arg->x.v[1]);
	vpr(&arg->y.v[0]);
	vpr(&arg->y.v[1]);
}

//void pecc_spu_add_get_args(spu_add_t* arg, spu_elem_t* el) {
mkpsgfunc(scalar_mult) {
	pecc_spu_get_elem((void*)&scalar_mult_arglist, el);
	pecc_spu_get_elem(&arg->s,scalar_mult_arglist.list+0);
	pecc_spu_get_elem(&arg->x,scalar_mult_arglist.list+1);
	pecc_spu_get_elem(&arg->y,scalar_mult_arglist.list+2);
}
//void pecc_spu_add_set_result(spu_add_t* arg, spu_elem_t* el) {
mkpssfunc(scalar_mult) {
//	pecc_spu_put_elem(el, (void*)arg);
	pecc_spu_put_elem(scalar_mult_arglist.list+1,&arg->x);
	pecc_spu_put_elem(scalar_mult_arglist.list+2,&arg->y);
}

